/*
 * Simulator
 * Date: December 09, 2011
 * Group JEDi
 * Author(s): James Lovato, Efim Todorasco, Daniel Garrison
 */
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

/**
 * A class that holds all the functionality of a process.
 * 
 * @author James Lovato, Efim Todorasco, Daniel Garrison
 * @version Fall 2011
 *
 */
public class Process {
	
	/**
	 * The max number of instructions a process can have.
	 */
	public static final int MAX_INSTRUCTIONS = 500;
	
	/**
	 * Shift used to insure the producers and consumers
	 * do not get there mandatory instructions over written.
	 */
	private static final int MAX_INSTRUCTION_SHIFT = 50;
	
	/**
	 * Time used to add delay time in between producer's and 
	 * consumer's instruction set.
	 */
	private static final int LOCK_TIME = 10;
	
	/**
	 * Total number of instructions the process has.
	 */
	int numOfInstr;
	
	/**
	 * Current instruction the process is on.
	 */
	int currInst;
	
	/**
	 * The process's Process Control Block for holding
	 * it's information while waiting in a queue.
	 */
	PCB pcBlock;
	
	/**
	 * A list of the Process's requests.
	 */
	List<ProcessRequest> myInterrupts = new ArrayList<ProcessRequest>();
	
	/**
	 * A random number generated used often in this class.
	 */
	Random rand = new Random();
	
	/**
	 * The type of Process this process is set to upon
	 * it's creation.
	 */
	ProcessType typeOfProcess;
	
	/**
	 * Constructor that has no random instructions.
	 */
	public Process() {
		pcBlock = new PCB();
		typeOfProcess = ProcessType.CALCULATOR;
		numOfInstr = rand.nextInt(MAX_INSTRUCTIONS);
		if (numOfInstr < MAX_INSTRUCTIONS / 2) {
			numOfInstr = MAX_INSTRUCTIONS / 2;
		}
		currInst = 0;
	}
	
	/**
	 * Constructor that creates a Process with a passed
	 * in amount of random requests.
	 * 
	 * @param numOfRequests The number of random requests desired.
	 */
	public Process(int numOfRequests) {
		pcBlock = new PCB();
		typeOfProcess = ProcessType.CALCULATOR;
		numOfInstr = rand.nextInt(MAX_INSTRUCTIONS);
		if (numOfInstr < MAX_INSTRUCTIONS / 2) {
			numOfInstr = MAX_INSTRUCTIONS / 2;
		}
		currInst = 0;
		randomInterrupts(numOfRequests);
	}
	
	/**
	 * @return Is there an interrupt associated with the current instruction?
	 */
	public Interrupts incProcess() {
		if (currInst == numOfInstr) {
			currInst = 0;
		} else {
			currInst++;
		}
		return checkForInterupt();
	}
	
	/**
	 * @param request The request to be added as a process request
	 * with a randomly generated time for the instruction.
	 */
	public void addInterrupt(final Interrupts request) {
		myInterrupts.add(new ProcessRequest(request, new Integer(rand.nextInt(this.numOfInstr))));  //do we need this method?
	}
	
	/**
	 * @param interupt  The interrupt to be added as a process request.
	 * @param time The time of when this request will be issued.
	 */
	public void addInterrupt(Interrupts interupt, int time) {
		myInterrupts.add(new ProcessRequest(interupt, time));
	}
	
	/**
	 * Fills up the list of Process Requests for the process.
	 * @param numOfRequests The number of random requests required.
	 */
	public void randomInterrupts(int numOfRequests) {
		int num = numOfRequests;
		Interrupts[] arr = Interrupts.values();
		
		while (num > 0) {
			myInterrupts.add(new ProcessRequest(arr[rand.nextInt(2)], randomNum()));
			num--;
		}
	}
	
	/**
	 * @return What instruction is a good time to call this request?
	 */
	public int randomNum() {
		int value = 0;
		value = rand.nextInt(numOfInstr - MAX_INSTRUCTION_SHIFT);
		while (checkIfInstructionExists(value + MAX_INSTRUCTION_SHIFT)) {
			value = rand.nextInt(numOfInstr - MAX_INSTRUCTION_SHIFT);
		}
		return value + MAX_INSTRUCTION_SHIFT;
	}
	/**
	 * @param instruction The instruction number.
	 * @return Is a Process Request already set for this instruction.
	 */
	private boolean checkIfInstructionExists(final int instruction) {
		boolean check = false;
		for (int i = 0; i < myInterrupts.size() - 1; i++) {
			if (instruction == myInterrupts.get(i).getIntTime()) {
				check = true;
			}
		}
		return check;
	}
	
	/**
	 * @return Is there a Process Request for this instruction?
	 */
	public Interrupts checkForInterupt() {
		Interrupts interupt = null;
		int count = 0;
		Iterator<ProcessRequest> itr = myInterrupts.iterator();
		while (itr.hasNext()) {			
			if (itr.next().getIntTime() == currInst) {
				interupt = myInterrupts.get(count).getIntType();
				return interupt; // return this interrupt
			}
			count++;
		}
		return interupt; // return null if no interrupt
	}
	
	/**
	 * Add certain Process Requests to create a Producer Process.
	 */
	public void addProducer() {
		addInterrupt(Interrupts.SEM_WRITE_DOWN, LOCK_TIME);
		addInterrupt(Interrupts.LOCK_MUTEX, LOCK_TIME * 2);
		addInterrupt(Interrupts.CRIT_REGION, LOCK_TIME * 3);
		addInterrupt(Interrupts.UNLOCK_MUTEX, LOCK_TIME * 4);
		addInterrupt(Interrupts.SEM_READ_UP, LOCK_TIME * 5);
	}
	
	/**
	 * Add certain Process Requests to create a Consumer Process.
	 */
	public void addConsumer() {
		addInterrupt(Interrupts.SEM_READ_DOWN, LOCK_TIME);
		addInterrupt(Interrupts.LOCK_MUTEX, LOCK_TIME * 2);		
		addInterrupt(Interrupts.CRIT_REGION, LOCK_TIME * 3);
		addInterrupt(Interrupts.UNLOCK_MUTEX, LOCK_TIME * 4);
		addInterrupt(Interrupts.SEM_WRITE_UP, LOCK_TIME * 5);		
	}
	
	/**
	 * @return What is the Process Request.
	 */
	public Interrupts getInterupt() {
		return myInterrupts.get(currInst).getIntType();
	}
	
	/**
	 * @param state The Process's new State.
	 */
	public void setState(State state) {
		pcBlock.state = state;
	}
	
	/**
	 * @return What is the Process's current State?
	 */
	public State getState() {
		return pcBlock.state;
	}
	
	/**
	 * @return What are the Process's Requests?
	 */
	public List<ProcessRequest> getListOfInterupts() {
		return myInterrupts;
	}
	
	public String getProcessString() {
		return "Process " + pcBlock.pid + "(" + this.typeOfProcess.toString() + ")";
	}
}
